//
//  PhotoBase.m
//

#import "PhotoBase.h"

#import "Controller.h"
#import "ExifController.h"
#import "FolderModel.h"
#import "IRController.h"
#import "OperationProgressController.h"
#import "PreferencesData.h"
#import "ThumbData.h"
#import "Utility.h"


#import "exif-data.h"
#import "Epeg.h"


@implementation PhotoBase

//------------------------------------------------------------------------------
- (void) dealloc
{
	[exifDialog release];

	[super dealloc];
}

//------------------------------------------------------------------------------
// These should really be abstract classes...
//------------------------------------------------------------------------------
- (void) clearSelection
{
}

//------------------------------------------------------------------------------
- (NSString*) currentFolder
{
	return  nil;
}

//------------------------------------------------------------------------------
- (FolderModel*) folderModel
{
	return nil;
}

//------------------------------------------------------------------------------
- (NSSize) maxThumbSize
{
	return NSMakeSize(0, 0);
}

//------------------------------------------------------------------------------
- (NSWindow *) mainWindow
{
	return nil;
}

//------------------------------------------------------------------------------
- (void) redrawThumbnails:(NSArray*) items
{
}

//------------------------------------------------------------------------------
- (void) selectCurrent
{
}

//------------------------------------------------------------------------------
- (NSMutableArray*) selectedItems
{
	return nil;
}

//------------------------------------------------------------------------------
- (IBAction) manageSlideshows:(id)sender
{
	[[Controller sharedController] manageSlideshows];
}

//------------------------------------------------------------------------------
- (IBAction) setToExifDate:(id)sender
{
	NSArray *selItems = [self selectedItems];
	if ([selItems count] > 0)
	{
		[[Controller sharedController] setToExifDate: selItems];
	}
}

//------------------------------------------------------------------------------
- (IBAction) shiftExifDate:(id)sender
{
	NSArray *selItems = [self selectedItems];
	if ([selItems count] > 0)
	{
		[[Controller sharedController] shiftExifDate: selItems];
		[self redrawThumbnails: selItems];
	}
}

//------------------------------------------------------------------------------
- (IBAction) shiftFileDate:(id)sender
{
	NSArray *selItems = [self selectedItems];
	if ([selItems count] > 0)
	{
		[[Controller sharedController] shiftFileDate: selItems];
		[self redrawThumbnails: selItems];
	}
}

//------------------------------------------------------------------------------
- (void) thumbnailUpdated:(ThumbData*) td
{
	[[self folderModel] updateItems: [NSArray arrayWithObject: td]];
}

//------------------------------------------------------------------------------
- (void) updateStatusBar
{
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
- (ThumbData *) selectedThumbData
{
	NSArray *selectedItems = [self selectedItems];
	if ([selectedItems count] > 0)
	{
		return [selectedItems objectAtIndex: 0];
	}

	return nil;
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
- (IBAction) copyImage:(id)sender
{
	NSOpenPanel *openPanel = [NSOpenPanel openPanel];
	[openPanel setCanChooseFiles:NO];
	[openPanel setCanChooseDirectories:YES];

	NSString *dir = [[Controller sharedController] lastMoveOrCopyFolder];
	if (nil == dir)
	{
		dir = [self currentFolder];
		if (nil == dir)
		{
			dir = GetPicturesFolder();
		}
		else
		{
			dir = [dir stringByDeletingLastPathComponent];
		}
	}

	[openPanel beginSheetForDirectory
		: dir
		file: nil
		types: nil
		modalForWindow: [self mainWindow]
		modalDelegate: self
		didEndSelector: @selector(copyPanelDidEnd: returnCode: contextInfo:)
		contextInfo: NULL];
}

//------------------------------------------------------------------------------
- (IBAction) copyImageToParent:(id)sender
{
	NSString *parent = [[self currentFolder] stringByDeletingLastPathComponent];
	[self moveOrCopySelectedItems: parent   copy: YES   clearSel: NO];
}

//------------------------------------------------------------------------------
- (IBAction) generateExifThumbnail:(id)sender
{
	[self startOperationOnSelectedItems:nil
			sel:@selector(generateThumbnail:thumb:dd:ret:)
			opType:OT_GEN_THUMB
			opString:NSLocalizedString(@"Generating thumbnail", @"generate.ExifThumbnail status bar")
			clearSel:NO
			updateThumb:YES];
}

//------------------------------------------------------------------------------
- (IBAction) getInfo:(id)sender
{
	ThumbData *td = [self selectedThumbData];
	if (nil != td)
	{
		[[Controller sharedController] showInfoDialog: &exifDialog  file:[td fullPath]];
	}
}

//------------------------------------------------------------------------------
- (IBAction) moveImage:(id)sender
{
	NSOpenPanel *openPanel = [NSOpenPanel openPanel];
	[openPanel setCanChooseFiles:NO];
	[openPanel setCanChooseDirectories:YES];

	NSString *dir = [[Controller sharedController] lastMoveOrCopyFolder];
	if (nil == dir)
	{
		dir = [self currentFolder];
		if (nil == dir)
		{
			dir = GetPicturesFolder();
		}
		else
		{
			dir = [dir stringByDeletingLastPathComponent];
		}
	}

	[openPanel beginSheetForDirectory
		: dir
		file: nil
		types: nil
		modalForWindow: [self mainWindow]
		modalDelegate: self
		didEndSelector: @selector(movePanelDidEnd: returnCode: contextInfo:)
		contextInfo: NULL];
}

//------------------------------------------------------------------------------
- (IBAction) moveImageToTrash:(id)sender
{
	NSArray *selItems = [self selectedItems];

	if (YES == [[[Controller sharedController] preferencesData] confirmMoveToTrash])
	{
		NSString *msg;
		NSString *subMsg;
		if (1 == [selItems count])
		{
			ThumbData *td = [selItems objectAtIndex: 0];
			msg = [NSString stringWithFormat
				: NSLocalizedString(@"Move %@ to the trash?", @"moveImageToTrash"), [td name]];
			subMsg = NSLocalizedString(@"You can recover it later.", @"moveImageToTrash");
		}
		else
		{
			msg = [NSString stringWithFormat
					: NSLocalizedString(@"Move the %d selected items to the trash?", @"moveImageToTrash"), 
						[selItems count]];
			subMsg = NSLocalizedString(@"You can recover them later.", @"moveImageToTrash");
		}

		int choice = NSRunAlertPanel(
				msg, 
				subMsg, 
				NSLocalizedString(@"Trash", @"alert button"), 
				NSLocalizedString(@"Cancel", @"alert button"),
				nil);
		if (NSAlertDefaultReturn != choice)
		{
			return;
		}
	}

	int count = [selItems count];
	NSMutableArray *filenames = [NSMutableArray arrayWithCapacity: count];
	int idx;
	for (idx = 0; idx < count; ++idx)
	{
		[filenames addObject: [[selItems objectAtIndex: idx] name]];
	}

	NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
	int ret = [workspace performFileOperation
		:NSWorkspaceRecycleOperation
		source: [self currentFolder]
		destination:@"" 
		files:filenames
		tag:NULL];

	if (YES == ret)
	{
		[[[Controller sharedController] trashSound] play];
		[[self folderModel] removeItems: selItems];
		[self clearSelection];
		[self selectCurrent];
		[self updateStatusBar];
	}
}

//------------------------------------------------------------------------------
- (IBAction) remoteControl:(id)sender
{
	[[IRController sharedController] startTopLevel];
}

//------------------------------------------------------------------------------
- (IBAction) rotateRight:(id)sender
{
	[self startOperationOnSelectedItems:nil
			sel:@selector(rotate90:thumb:dd:ret:)
			opType:OT_ROTATE
			opString:NSLocalizedString(@"Rotating", @"rotate status bar message")
			clearSel:NO
			updateThumb:YES];
}

//------------------------------------------------------------------------------
- (IBAction) rotateLeft:(id)sender
{
	[self startOperationOnSelectedItems:nil
			sel:@selector(rotate270:thumb:dd:ret:)
			opType:OT_ROTATE
			opString:NSLocalizedString(@"Rotating", @"rotate status bar message")
			clearSel:NO
			updateThumb:YES];
}

//------------------------------------------------------------------------------
- (void) selectionChanged:(ThumbData *) td
{
	if ([[exifDialog mainWindow] isVisible])
	{
		[exifDialog setFile: [td fullPath]];
	}
}

//------------------------------------------------------------------------------
// Copy/move helpers
//------------------------------------------------------------------------------
- (void) copyPanelDidEnd
	:(NSOpenPanel*) openPanel
	returnCode:(int) returnCode  
	contextInfo:(void*) contextInfo
{
	if (NSOKButton != returnCode || nil == [openPanel filename])
	{
		return;
	}

	[[Controller sharedController] setLastMoveOrCopyFolder: [openPanel filename]];
	[self moveOrCopySelectedItems: [openPanel filename]  copy: YES  clearSel: NO];
}

//------------------------------------------------------------------------------
- (void) movePanelDidEnd
	:(NSOpenPanel*) openPanel
	returnCode:(int) returnCode  
	contextInfo:(void*) contextInfo
{
	if (NSOKButton != returnCode || nil == [openPanel filename])
	{
		return;
	}

	[[Controller sharedController] setLastMoveOrCopyFolder: [openPanel filename]];
	[self moveOrCopySelectedItems: [openPanel filename]  copy: NO  clearSel:YES];
}

//------------------------------------------------------------------------------
- (void) moveOrCopySelectedItems
	:(NSString *) destDir
	copy: (BOOL) copy
	clearSel: (BOOL) clearSel
{
	SEL selector;
	OperationType opType;
	NSString *operationString;
	if (YES == copy)
	{
		selector = @selector(copy:thumb:dd:ret:);
		opType = OT_COPY;
		operationString = NSLocalizedString(@"Copying", @"copying status bar message");
	}
	else
	{
		selector = @selector(move:thumb:dd:ret:);
		opType = OT_MOVE;
		operationString = NSLocalizedString(@"Moving", @"moving status bar message");
	}

	[self startOperationOnSelectedItems:destDir
			sel:selector
			opType:opType
			opString:operationString
			clearSel:clearSel
			updateThumb:NO];
}


//------------------------------------------------------------------------------
// Operation methods
//------------------------------------------------------------------------------
- (void) startOperationOnSelectedItems
	:(NSString*) destDir
	sel:(SEL)selOp
	opType:(OperationType)opType
	opString:(NSString*)opString
	clearSel:(BOOL)clearSel
	updateThumb:(BOOL)updateThumb
{
	NSMutableArray *operationList = [self selectedItems];

	OperationStartData *osd = [[OperationStartData alloc] init];
	[osd set:destDir
		sel:selOp
		thumbList:operationList
		clearSel:clearSel
		updateThumb:updateThumb
		opType:opType];

	operationStop = NO;
	operationInProgress = YES;

	operProgressDialog = [[OperationProgressController alloc] init];
	[NSBundle loadNibNamed:@"OperationProgressDialog"  owner:operProgressDialog];
	[operProgressDialog 
		setup: [operationList count]
		operationString: opString
		dest: destDir];

	[NSThread 
		detachNewThreadSelector: @selector(operationThread:)
		toTarget: self
		withObject: osd];

	// In a short period of time, get the operation progress dialog to appear.
	// The goal is for the progress dialog to NOT appear for small (one or two image) 
	// operations, but to appear for any lengthy operation.
	[self
		performSelector: @selector(showOperationDialog:)
		withObject: nil
		afterDelay: 0.5];
}

//------------------------------------------------------------------------------
- (void) showOperationDialog:(id) value
{
	if (YES == operationInProgress)
	{
		int ret = [NSApp runModalForWindow: [operProgressDialog mainWindow]];
		if (NSRunStoppedResponse == ret)
		{
			// Tell the operation thread to stop
			operationStop = YES;
		}

		[[operProgressDialog mainWindow] close];
	}

	[operProgressDialog release];
	operProgressDialog = nil;
	operationInProgress = NO;
}

//---------------------------------------------------------------------------------
/* Called by the operation thread (and running in the GUI thread) just before the
 * file operation takes place, sending the filename.
 */
- (void) operationToStart:(id) data
{
	[operProgressDialog startingFile: [data name]];
}

//---------------------------------------------------------------------------------
/* Called by the operation thread (and running in the GUI thread), this method takes 
 * an OperationCompleteData and updates the UI with the results.
 */
- (void) operationCompleted:(id) data
{
	operationInProgress = NO;
	[operProgressDialog operationComplete];


	OperationCompleteData *ocd = data;
	if (YES == ocd->clearSel && noErr == ocd->lastRet)
	{
		[self clearSelection];

		// If we were stopped by the user, we only want to clear the items operated on
		while (ocd->completedCount < [ocd->thumbList count])
		{
			[ocd->thumbList removeLastObject];
		}

		[[self folderModel] removeItems: ocd->thumbList];
		[self updateStatusBar];
	}


	if (noErr != ocd->lastRet)
	{
		NSString *format;
		switch (ocd->opType)
		{
			case OT_COPY:
				format = NSLocalizedString(@"Failed copying %@ to '%@'.", @"copy failed");
				break;

			case OT_MOVE:
				format = NSLocalizedString(@"Failed moving %@ to '%@'.", @"move failed");
				break;

			case OT_ROTATE:
				format = NSLocalizedString(@"Failed rotating %@.", @"rotate failed");
				break;

			case OT_GEN_THUMB:
				format = NSLocalizedString(@"Failed generating thumb for %@.", @"thumb generation failed");
				break;

			default:
				NSLog(@"Unexpected operation type, %d (code: %d)", ocd->opType, ocd->lastRet);
				format = NSLocalizedString(@"Operation on %@ failed.", @".unexpected operation failed");
				break;
		}

		NSRunAlertPanel(
			[NSString stringWithFormat: format, [ocd->tdLast name], [ocd->destDir lastPathComponent]],
			[[Controller sharedController] stringFromErrorCode: ocd->lastRet],
			NSLocalizedString(@"Close", @"Close"), 
			nil, 
			nil);
	}
}

//------------------------------------------------------------------------------
/* Thread method that takes an OperationStartData - it operates on a set
 * of ThumbData instances.
 */
- (void) operationThread: (id) data
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];


	NSWorkspace *workspace = [NSWorkspace sharedWorkspace];

	// Do the files one at a time so we can:
	//  a) control when to stop 
	//	b) notify the GUI as each file completes.
	ThumbData *td = nil;
	int lastRet = noErr;
	int completedCount = 0;

	OperationStartData *osd = data;
	int count = [osd->thumbList count];
	int idx;
	for (idx = 0; NO == operationStop && idx < count && noErr == lastRet; ++idx)
	{
		++completedCount;
		td = [osd->thumbList objectAtIndex: idx];

		// Notify the main thread of what we're about to do (to update 
		// the progress dialog and so on)
		[self 
			performSelectorOnMainThread: @selector(operationToStart:)
			withObject: td
			waitUntilDone: NO];

		objc_msgSend(self, osd->selOp, workspace, td, osd->destDir, &lastRet);

		if (YES == osd->updateThumb)
		{
			// The thumbnail may need to be updated once it's been operated on.
			[self 
				performSelectorOnMainThread: @selector(thumbnailUpdated:)
				withObject: td
				waitUntilDone: NO];
		}
	}

	// Notify the main thread that we're done, letting it know if we errored out or not.
	OperationCompleteData *ocd = [[OperationCompleteData alloc] init];
	ocd->clearSel = osd->clearSel;
	ocd->lastRet = lastRet;
	ocd->completedCount = completedCount;
	[ocd set:osd->destDir  thumbList:osd->thumbList  tdLast:td  opType:osd->opType];

	[self 
		performSelectorOnMainThread: @selector(operationCompleted:)
		withObject: ocd
		waitUntilDone: NO];

	[ocd release];
	[osd release];

	[pool release];
	[NSThread exit];
}


//------------------------------------------------------------------------------
//		Operation thread functions (move, copy, rotate...)
//------------------------------------------------------------------------------
- (void) move:(NSWorkspace*)workspace  thumb:(ThumbData*)td  dd:(NSString*)destDir  ret:(int *) pRet
{
/*
NSLog(@"move  file:%@  dst:%@", [td name], destDir);
usleep(300000);
return noErr;
*/
	NSMutableArray *filenames = [NSMutableArray arrayWithObject: [td name]];
	*pRet = 0;
	[[self folderModel] setOperationFile: [td name]];
	[workspace 
			performFileOperation: NSWorkspaceMoveOperation
			source: [[td fullPath] stringByDeletingLastPathComponent]
			destination: destDir
			files: filenames
			tag: pRet];
	[[self folderModel] setOperationFile: nil];
}

//------------------------------------------------------------------------------
- (void) copy:(NSWorkspace*)workspace  thumb:(ThumbData*)td  dd:(NSString*)destDir  ret:(int *) pRet
{
/*
NSLog(@"copy  file:%@  dst:%@", [td name], destDir);
usleep(300000);
return noErr;
*/

	UInt8 *utfPath = (UInt8*)[[td fullPath] UTF8String];
	FSRef sourceRef;
	Boolean isDirectory;
	*pRet = FSPathMakeRef(utfPath, &sourceRef, &isDirectory);
	if (0 != *pRet)
	{
		return;
	}

	FSCatalogInfo info;
	*pRet = FSGetCatalogInfo(&sourceRef, kFSCatInfoSettableInfo, &info, nil, nil, nil);
	if (0 != *pRet)
	{
		return;
	}

	NSMutableArray *filenames = [NSMutableArray arrayWithObject: [td name]];
	[workspace 
			performFileOperation: NSWorkspaceCopyOperation
			source: [[td fullPath] stringByDeletingLastPathComponent]
			destination: destDir
			files: filenames
			tag: pRet];

	// Have the destination file use the same attributes as the source file
	if (0 == *pRet)
	{
		NSString *dstFile = [destDir stringByAppendingPathComponent: [td name]];
		utfPath = (UInt8*)[dstFile UTF8String];
		FSRef destRef;
		*pRet = FSPathMakeRef(utfPath, &destRef, &isDirectory);
		if (0 == *pRet)
		{
			*pRet = FSSetCatalogInfo(&destRef, kFSCatInfoSettableInfo, &info);
		}
	}
}

//------------------------------------------------------------------------------
- (void) generateThumbnail:	(NSWorkspace*)workspace  thumb:(ThumbData*)td  dd:(NSString*)destDir  ret:(int *) pRet
{
	NSString *file = [td fullPath];
	[[self folderModel] setOperationFile: [td name]];
	*pRet = generateExifThumbnail(file);
	[[self folderModel] setOperationFile: nil];

	if (noErr == *pRet)
	{
		NSImage *thumb = [self loadExifThumbnail: file];
		[td setThumbnail: thumb];
		[td calculateState];
		[thumb release];
	}
}

//------------------------------------------------------------------------------
- (void) rotate90:(NSWorkspace*)workspace  thumb:(ThumbData*)td  dd:(NSString*)destDir  ret:(int *) pRet
{
	NSString *file = [td fullPath];
	[[self folderModel] setOperationFile: [td name]];
	*pRet = rotate90(file);
	[[self folderModel] setOperationFile: nil];

	if (noErr == *pRet)
	{
		NSImage *thumb = [self loadExifThumbnail: file];
		[td setThumbnail: thumb];
		[thumb release];
	}
}

//------------------------------------------------------------------------------
- (void) rotate270:(NSWorkspace*)workspace  thumb:(ThumbData*)td  dd:(NSString*)destDir  ret:(int *) pRet
{
	NSString *file = [td fullPath];
	[[self folderModel] setOperationFile: [td name]];
	*pRet = rotate270(file);
	[[self folderModel] setOperationFile: nil];

	if (noErr == *pRet)
	{
		NSImage *thumb = [self loadExifThumbnail: file];
		[td setThumbnail: thumb];
		[thumb release];
	}
}

//------------------------------------------------------------------------------
/**
  * Caller is responsible for releasing the returned image.
  */
- (NSImage*) loadExifThumbnail:(NSString*) filename
{
	NSImage *thumbnail = nil;

	ExifData *pExif = exif_data_new_from_file([filename cStringUsingEncoding:NSUTF8StringEncoding]);
	if (nil != pExif)
	{
		// Grab the thumbnail, if it exists
		if (0 != pExif->size)
		{
			// Determine the actual image width/height
			Epeg_Image *epeg = epeg_file_open([filename cStringUsingEncoding:NSUTF8StringEncoding]);
			int aWidth, aHeight;
			epeg_size_get(epeg, &aWidth, &aHeight);
			epeg_close(epeg);
			BOOL actualWidthBigger = aWidth >= aHeight;

			NSData *imageData = [NSData dataWithBytesNoCopy
					: pExif->data
					length: pExif->size
					freeWhenDone: NO];
			thumbnail = [[NSImage alloc] initWithData: imageData];
			NSSize sizeThumbnail = [thumbnail size];
			BOOL widthBigger = sizeThumbnail.width >= sizeThumbnail.height;

			// Make sure the thumbnail has the same aspect ratio as the image
			//  If it doesn't, get rid of the thumbnail (and generate it later)
			if (widthBigger != actualWidthBigger)
			{
				[thumbnail release];
				thumbnail = nil;
			}
		}

		exif_data_free(pExif);
	}

	if (nil == thumbnail)
	{
		NSSize max = [self maxThumbSize];
		thumbnail = generateThumbnail(filename, max.width, max.height);
	}

	return thumbnail;
}

@end
